package com.erdos.common.dao;

import java.lang.reflect.ParameterizedType;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.ibatis.SqlMapClientCallback;
import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;
import org.springframework.util.Assert;

import com.erdos.common.entity.AbstractEntity;
import com.erdos.common.exception.ConcurrentModifyException;
import com.erdos.common.exception.DaoException;
import com.erdos.common.pagination.LimitSqlExecutor;
import com.erdos.common.pagination.Page;
import com.erdos.common.util.AppContext;
import com.erdos.common.util.ReflectUtil;
import com.erdos.common.web.LoginUtil;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapExecutor;
import com.ibatis.sqlmap.engine.execution.SqlExecutor;
import com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient;


public class IBatisGenericDaoImpl<T extends AbstractEntity> extends
		SqlMapClientDaoSupport implements DaoBase<T> {

	protected final Log logger = LogFactory.getLog(this.getClass());

	private static final int BATCH_SIZE = 50;

	// model's Class
	protected Class<T> entityClass;

	// model's ClassName
	protected String entityClassName;

	private SqlExecutor sqlExecutor;

	public SqlExecutor getSqlExecutor() {
		return sqlExecutor;
	}

	public void setSqlExecutor(SqlExecutor sqlExecutor) {
		this.sqlExecutor = sqlExecutor;
	}

	public void setEnableLimit(boolean enableLimit) {
		if (sqlExecutor instanceof LimitSqlExecutor) {
			((LimitSqlExecutor) sqlExecutor).setEnableLimit(enableLimit);
		}
	}
	
	protected boolean openSingleHistoryTrack=true;
	
	protected boolean openGlobalHistoryTrack=true;

	public void setOpenSingleHistoryTrack(boolean openSingleHistoryTrack) {
		this.openSingleHistoryTrack = openSingleHistoryTrack;
	}

	public void setOpenGlobalHistoryTrack(boolean openGlobalHistoryTrack) {
		this.openGlobalHistoryTrack = openGlobalHistoryTrack;
	}

	@SuppressWarnings("unchecked")
	public IBatisGenericDaoImpl() {
		try {			
			Object genericClz=getClass().getGenericSuperclass();
			
			if(genericClz instanceof ParameterizedType) {				
				entityClass = (Class<T>) ((ParameterizedType)genericClz).getActualTypeArguments()[0];
				entityClassName = entityClass.getSimpleName();
			}

			//logger.debug("dao entityClassName=" + entityClassName);
		} catch (RuntimeException e) {
			e.printStackTrace();
		}
	}

	public void initialize() {
		try {
			if (sqlExecutor != null) {
				SqlMapClient sqlMapClient = getSqlMapClientTemplate()
						.getSqlMapClient();
				if (sqlMapClient instanceof ExtendedSqlMapClient) {

					ReflectUtil
							.setFieldValue(
									((ExtendedSqlMapClient) sqlMapClient)
											.getDelegate(), "sqlExecutor",
									SqlExecutor.class, sqlExecutor);
				}
			}
		} catch (RuntimeException e) {
			e.printStackTrace();
		}
	}

	/**
	 * get the entity class name ，such as "User"
	 */
	protected String getEntityName() {
		return this.entityClassName;
		//return ClassUtils.getShortName(GenericsUtils.getGenericClass(getClass()));
	}

	@SuppressWarnings("unchecked")
	public Page<T> findByPage(Map<String, Object> params, int pageNo, int limit) {
		params.put(DaoBase.GLOBAL_FACILITY_KEY, AppContext.getFacility());
		Page<T> page = new Page<T>();
		page.setPageNo(pageNo);
		page.setLimit(limit);
		String findSqlMapId = getEntityName() + "." + PRE_FIND;
		logger.debug("findSqlMapId=" + findSqlMapId);
		try {
			List<T> resultList = getSqlMapClientTemplate().queryForList(
					findSqlMapId, params, page.getOffset(), limit);
			page.setResultList(resultList);
		} catch (DataAccessException e) {
			throw new DaoException("jdbc.error.code.Common.findByPage", new String[]{entityClass.getName()},e);
		}
		page.setTotalRows(((LimitSqlExecutor) getSqlExecutor())
				.getTotalRowsCount());
		return page;
	}

	public Long insert(T entity) {
		Assert.notNull(entity);
		String insertSqlMapId = getEntityName() + "." + PRE_INSERT;
		logger.debug("insertSqlMapId=" + insertSqlMapId);
		if(StringUtils.isEmpty(entity.getCreatedBy())){
		    entity.setCreatedBy(LoginUtil.getUpcUserPin());
		}
		Long sid=null;
		try {
			sid=(Long)this.getSqlMapClientTemplate().insert(insertSqlMapId, entity);
			afterInsert(entity);
		} catch (DataAccessException e) {
			logger.error(e);
			throw new DaoException("jdbc.error.code.Common.insert", new String[]{entityClass.getName()},e);
		}
		
		return sid;
	}

	/**
	 * �����¼��Ļص�������ڴ��������ʷ��¼���������
	 * @param sid ��ִ�в��붯����д�����
	 */
	protected void afterInsert(T entity){
		
	}
	
	
	public void insertAll(final Collection<T> collection) {
		if (collection == null || collection.isEmpty())
			return;
		try {
			this.getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
				public Object doInSqlMapClient(SqlMapExecutor executor)
						throws SQLException {
					executor.startBatch();
					// do iBatis Batch operations here
					int i = 0;
					String insertSqlMapId = getEntityName() + "." + PRE_INSERT;
					logger.debug("insertSqlMapId=" + insertSqlMapId);
					for (T entity : collection) {
				        if(StringUtils.isEmpty(entity.getCreatedBy())){
				            entity.setCreatedBy(LoginUtil.getUpcUserPin());
				        }
						executor.insert(insertSqlMapId, entity);
						if (i++ > 0 && i % BATCH_SIZE == 0) {
							executor.executeBatch();
						}
					}
					executor.executeBatch();
					return null;
				}
			});
		} catch (DataAccessException e) {
			logger.error(e);
			throw new DaoException("jdbc.error.code.Common.insertAll", new String[]{entityClass.getName()},e);
		}
	}

	public void update(T entity) {
		String updateSqlMapId = getEntityName() + "." + PRE_UPDATE;
		logger.debug("updateSqlMapId=" + updateSqlMapId);
		entity.setUpdatedBy(LoginUtil.getUpcUserPin());
		entity.setUpdatedDt(new Date());
		try {
			int effectCnt=this.getSqlMapClientTemplate().update(updateSqlMapId, entity);
			if(effectCnt==0){
				throw new ConcurrentModifyException();
			}
			entity.setVersion(entity.getVersion()+1);
			afterUpdate(entity);
		} catch (DataAccessException e) {
			logger.error(e);
			throw new DaoException("jdbc.error.code.Common.update", new String[] { entityClass.getName(),entity.getSid().toString()},e);
		}
	}
	
	/**
	 * ���¼�¼��Ļص�������ڴ��������ʷ��¼���������
	 * @param sid ��ִ�и��¶�����д�����
	 */
	protected void afterUpdate(T entity){
		logger.info("execute!");
	}

	public void updateAll(final Collection<T> collection) {
		if (collection == null || collection.isEmpty())
			return;
		try {
//			this.getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
//				public Object doInSqlMapClient(SqlMapExecutor executor)
//						throws SQLException {
//					executor.startBatch();
//					// do iBatis Batch operations here
//					int i = 0;
//					String updateSqlMapId = getEntityName() + "." + PRE_UPDATE;
//					logger.debug("updateSqlMapId=" + updateSqlMapId);
//					for (T entity : collection) {
//						entity.setUpdatedBy(LoginUtil.getUpcUserPin());
//						entity.setUpdatedDt(new Date());
//						//executor.update(updateSqlMapId, entity);
//						
//						int effectCnt=executor.update(updateSqlMapId, entity);
//						if(effectCnt==0){
//							throw new ConcurrentModifyException();
//						}else{
//						    entity.setVersion(entity.getVersion()+1);
//						}
//						if (i++ > 0 && i % BATCH_SIZE == 0) {
//							executor.executeBatch();
//						}
//					}
//					executor.executeBatch();
//					return null;
//				}
//			});
		   
		   int i=0; 
          String updateSqlMapId = getEntityName() + "." + PRE_UPDATE;
		    for (T entity : collection) {
              entity.setUpdatedBy(LoginUtil.getUpcUserPin());
              entity.setUpdatedDt(new Date());
              //executor.update(updateSqlMapId, entity);
              
              int effectCnt=this.getSqlMapClientTemplate().update(updateSqlMapId, entity);
              if(effectCnt==0){
                  throw new ConcurrentModifyException();
              }else{
                  entity.setVersion(entity.getVersion()+1);
              }
              
          }
		} catch (DataAccessException e) {
			throw new DaoException("jdbc.error.code.Common.updateAll", new String[] { entityClass.getName()},e);
		}
	}

	public List<T> findAll() {
		return findByParams(null);
	}

	public int delete(T entity) {
		String deleteSqlMapId = getEntityName() + "." + PRE_DELETE;
		logger.debug("deleteSqlMapId=" + deleteSqlMapId);
		try {
			 int state = this.getSqlMapClientTemplate().delete(deleteSqlMapId,
					((AbstractEntity) entity).getSid());
			afterDelete(entity);
			return state;
		} catch (DataAccessException e) {
			logger.error(e);
			throw new DaoException("jdbc.error.code.Common.delete", new String[] { entityClass.getName(),entity.getSid().toString()},e);
		}
	}
	
	protected void afterDelete(T entity){
		
	}
	
	public int deleteByParams(Map<String,Object> params) {
		String deleteSqlMapId = getEntityName() + "." + PRE_DELETE_BY_PARAMS;
		logger.debug("deleteSqlMapId=" + deleteSqlMapId);
		try {
			return this.getSqlMapClientTemplate().delete(deleteSqlMapId,params);
		} catch (DataAccessException e) {
			logger.error(e);
			throw new DaoException("jdbc.error.code.Common.delete", new String[] { entityClass.getName(),params.toString()},e);
		}
	}

	public int deleteByPrimaryKey(Long sid) {
		String deleteSqlMapId = getEntityName() + "." + PRE_DELETE;
		logger.debug("deleteSqlMapId=" + deleteSqlMapId);
		try {
			return this.getSqlMapClientTemplate().delete(deleteSqlMapId, sid);
		} catch (DataAccessException e) {
			logger.error(e);
			throw new DaoException("jdbc.error.code.Common.delete", new String[] { entityClass.getName(),sid.toString()},e);
		}
	}
	
	public int deleteLogicByPrimaryKey(Long sid) {
		//todo
		return 1;
	}

	public void deleteAll(final Collection<T> collection) {
		if (collection == null || collection.isEmpty())
			return;
		try {
			this.getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
				public Object doInSqlMapClient(SqlMapExecutor executor)
						throws SQLException {
					executor.startBatch();
					// do iBatis Batch operations here
					int i = 0;
					String deleteSqlMapId = getEntityName() + "." + PRE_DELETE;
					logger.debug("deleteSqlMapId=" + deleteSqlMapId);
					for (T entity : collection) {
						executor.delete(deleteSqlMapId,
								((AbstractEntity) entity).getSid());
						if (i++ > 0 && i % BATCH_SIZE == 0) {
							executor.executeBatch();
						}
					}
					executor.executeBatch();
					return null;
				}
			});
		} catch (DataAccessException e) {
			logger.error(e);
			throw new DaoException("jdbc.error.code.Common.deleteAll", new String[] { entityClass.getName()},e);
		}
	}

	public void deleteAllByPrimaryKey(final Collection<Long> sids) {
		if (sids == null || sids.isEmpty())
			return;
		try {
			this.getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
				public Object doInSqlMapClient(SqlMapExecutor executor)
						throws SQLException {
					executor.startBatch();
					// do iBatis Batch operations here
					int i = 0;
					String deleteSqlMapId = getEntityName() + "." + PRE_DELETE;
					logger.debug("deleteSqlMapId=" + deleteSqlMapId);
					for (Long sid : sids) {
						executor.delete(deleteSqlMapId, sid);
						if (i++ > 0 && i % BATCH_SIZE == 0) {
							executor.executeBatch();
						}
					}
					executor.executeBatch();
					return null;
				}
			});
		} catch (DataAccessException e) {
			logger.error(e);
			throw new DaoException("jdbc.error.code.Common.deleteAll", new String[] { entityClass.getName()},e);
		}
	}

	public T findByPrimaryKey(Long id) {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("sid", id);
		return findUniqueByParams(params);
	}

	public List<T> findByPrimaryKeys(Long[] sids) {
		Map<String, Object> params = new HashMap<String, Object>();
		if (sids == null || sids.length == 0) {
			params.put("sids", new Long(-1));
		} else {
			StringBuffer sb = new StringBuffer();
			for (Long sid : sids) {
				sb.append("," + sid);
			}
			params.put("sids", sb.deleteCharAt(0).toString());
		}
		return findByParams(params);
	}

	@SuppressWarnings("unchecked")
	public T findUniqueByParams(Map<String, Object> params) {
		String findAllSqlMapId = getEntityName() + "." + PRE_FIND;
		logger.debug("findAllSqlMapId=" + findAllSqlMapId);
		try {
		    List<T> list= this.getSqlMapClientTemplate().queryForList(
                    findAllSqlMapId, params);
		    if(list==null||list.size()==0){
		        return null;
		    }else if(list.size()==1){
		        return list.get(0);
		    }else{
		        throw new DaoException("jdbc.error.code.Common.findUniqueByParams.notUique", new String[] { entityClass.getName(),params.toString()},null);
		    }
		} catch (DataAccessException e) {
			logger.error(e);
			throw new DaoException("jdbc.error.code.Common.findUniqueByParams", new String[] { entityClass.getName(),params.toString()},e);
		}
	}

	@SuppressWarnings("unchecked")
	public List<T> findByParams(Map<String, Object> params) {
		if(params==null){
			params=new HashMap<String, Object>();
		}
		String findSqlMapId = getEntityName() + "." + PRE_FIND;
	//	logger.debug("findSqlMapId=" + findSqlMapId);
		try {
			return (List<T>) this.getSqlMapClientTemplate().queryForList(
					findSqlMapId, params);
		} catch (DataAccessException e) {
			logger.error(e);
			throw new DaoException("jdbc.error.code.Common.findByParams", new String[] { entityClass.getName(),params.toString()},e);
		}
	}

	public Object findObjectBySqlID(String sqlID, Map<String, Object> params) {
		try {
			return this.getSqlMapClientTemplate().queryForObject(sqlID, params);
		} catch (DataAccessException e) {
			throw new DaoException("jdbc.error.code.Common.findObjectBySqlID", new String[] {sqlID,params.toString()},e);
		}
	}

	@SuppressWarnings("unchecked")
	public List findListBySqlID(String sqlID, Map<String, Object> params) {
		try {
			return this.getSqlMapClientTemplate().queryForList(sqlID, params);
		} catch (DataAccessException e) {
			throw new DaoException("jdbc.error.code.Common.findListBySqlID", new String[] {sqlID,params.toString()},e);
		}
	}
	
	public void insertHistory(Map<String,Object> params){
		if(!openGlobalHistoryTrack){
			logger.info("openGlobalHistoryTrack=false,skip historty record!");
			return;
		}
		if(!openSingleHistoryTrack){
			logger.info("openSingleHistoryTrack=false,skip historty record!");
			return;
		}
		String insertSqlMapId = getEntityName() + "." + PRE_INSERT_HISTORY;
		logger.debug("insertHistorySqlMapId=" + insertSqlMapId);
		try {
			this.getSqlMapClientTemplate().insert(insertSqlMapId, params);
		} catch (DataAccessException e) {
			throw new DaoException("jdbc.error.code.Common.insertHistory", new String[]{entityClass.getName(),params.toString()},e);
		}
	}

	/**
	 * @param entityClass the entityClass to set
	 */
	public void setEntityClass(Class<T> entityClass) {
		this.entityClass = entityClass;
		entityClassName = entityClass.getSimpleName();
		logger.debug("dao entityClassName=" + entityClassName);
	}
	
	
}
